﻿using Mall.Repository.Repository;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;

namespace Mall.Repository.RepositoryProgram
{
    internal class BinaryExpressionToSql : BaseExpressionToSql<BinaryExpression>
    {
        private void OperatorParser(ExpressionType expressionNodeType, int operatorIndex, SqlBuilder sqlBuilder, bool useIs = false)
        {
            switch (expressionNodeType)
            {
                case ExpressionType.And:
                case ExpressionType.AndAlso:
                    sqlBuilder.Insert(operatorIndex, "\nand");
                    break;
                case ExpressionType.Equal:
                    if (useIs)
                    {
                        sqlBuilder.Insert(operatorIndex, " is");
                    }
                    else
                    {
                        sqlBuilder.Insert(operatorIndex, " =");
                    }

                    break;
                case ExpressionType.GreaterThan:
                    sqlBuilder.Insert(operatorIndex, " >");
                    break;
                case ExpressionType.GreaterThanOrEqual:
                    sqlBuilder.Insert(operatorIndex, " >=");
                    break;
                case ExpressionType.NotEqual:
                    if (useIs)
                    {
                        sqlBuilder.Insert(operatorIndex, " is not");
                    }
                    else
                    {
                        sqlBuilder.Insert(operatorIndex, " <>");
                    }

                    break;
                case ExpressionType.Or:
                case ExpressionType.OrElse:
                    sqlBuilder.Insert(operatorIndex, "\nor");
                    break;
                case ExpressionType.LessThan:
                    sqlBuilder.Insert(operatorIndex, " <");
                    break;
                case ExpressionType.LessThanOrEqual:
                    sqlBuilder.Insert(operatorIndex, " <=");
                    break;
                default:
                    throw new NotImplementedException("Unimplemented expressionType:" + expressionNodeType);
            }
        }

        private static int GetOperatorPrecedence(Expression node)
        {
            switch (node.NodeType)
            {
                case ExpressionType.Add:
                case ExpressionType.AddChecked:
                case ExpressionType.Subtract:
                case ExpressionType.SubtractChecked:
                    return 10;
                case ExpressionType.And:
                    return 6;
                case ExpressionType.AndAlso:
                    return 3;
                case ExpressionType.Coalesce:
                case ExpressionType.Assign:
                case ExpressionType.AddAssign:
                case ExpressionType.AndAssign:
                case ExpressionType.DivideAssign:
                case ExpressionType.ExclusiveOrAssign:
                case ExpressionType.LeftShiftAssign:
                case ExpressionType.ModuloAssign:
                case ExpressionType.MultiplyAssign:
                case ExpressionType.OrAssign:
                case ExpressionType.PowerAssign:
                case ExpressionType.RightShiftAssign:
                case ExpressionType.SubtractAssign:
                case ExpressionType.AddAssignChecked:
                case ExpressionType.MultiplyAssignChecked:
                case ExpressionType.SubtractAssignChecked:
                    return 1;
                case ExpressionType.Constant:
                case ExpressionType.Parameter:
                    return 15;
                case ExpressionType.Convert:
                case ExpressionType.ConvertChecked:
                case ExpressionType.Negate:
                case ExpressionType.UnaryPlus:
                case ExpressionType.NegateChecked:
                case ExpressionType.Not:
                case ExpressionType.Decrement:
                case ExpressionType.Increment:
                case ExpressionType.Throw:
                case ExpressionType.Unbox:
                case ExpressionType.PreIncrementAssign:
                case ExpressionType.PreDecrementAssign:
                case ExpressionType.OnesComplement:
                case ExpressionType.IsTrue:
                case ExpressionType.IsFalse:
                    return 12;
                case ExpressionType.Divide:
                case ExpressionType.Modulo:
                case ExpressionType.Multiply:
                case ExpressionType.MultiplyChecked:
                    return 11;
                case ExpressionType.Equal:
                case ExpressionType.NotEqual:
                    return 7;
                case ExpressionType.ExclusiveOr:
                    return 5;
                case ExpressionType.GreaterThan:
                case ExpressionType.GreaterThanOrEqual:
                case ExpressionType.LessThan:
                case ExpressionType.LessThanOrEqual:
                case ExpressionType.TypeAs:
                case ExpressionType.TypeIs:
                case ExpressionType.TypeEqual:
                    return 8;
                case ExpressionType.LeftShift:
                case ExpressionType.RightShift:
                    return 9;
                case ExpressionType.Or:
                    return 4;
                case ExpressionType.OrElse:
                    return 2;
                case ExpressionType.Power:
                    return 13;
                default:
                    return 14;
            }
        }

        private static bool NeedsParenthesesLast(Expression parent, Expression child)
        {
            if (parent is BinaryExpression binaryExpression)
            {
                return child == binaryExpression.Right;
            }

            return false;
        }

        private static bool NeedsParenthesesPrecedence(Expression parent, Expression child)
        {
            int operatorPrecedence = GetOperatorPrecedence(child);
            int operatorPrecedence2 = GetOperatorPrecedence(parent);
            if (operatorPrecedence == operatorPrecedence2)
            {
                ExpressionType nodeType = parent.NodeType;
                if (nodeType <= ExpressionType.MultiplyChecked)
                {
                    if (nodeType <= ExpressionType.Divide)
                    {
                        switch (nodeType)
                        {
                            case ExpressionType.And:
                            case ExpressionType.AndAlso:
                                return false;
                            default:
                                return true;
                            case ExpressionType.Divide:
                                return NeedsParenthesesLast(parent, child);
                            case ExpressionType.Add:
                            case ExpressionType.AddChecked:
                                break;
                        }
                    }
                    else
                    {
                        switch (nodeType)
                        {
                            case ExpressionType.ExclusiveOr:
                                return false;
                            case ExpressionType.Modulo:
                                return NeedsParenthesesLast(parent, child);
                            default:
                                return true;
                            case ExpressionType.Multiply:
                            case ExpressionType.MultiplyChecked:
                                break;
                        }
                    }

                    return false;
                }

                if (nodeType <= ExpressionType.OrElse)
                {
                    if (nodeType != ExpressionType.Or && nodeType != ExpressionType.OrElse)
                    {
                        return true;
                    }

                    return false;
                }

                if (nodeType != ExpressionType.Subtract && nodeType != ExpressionType.SubtractChecked)
                {
                    return true;
                }

                return NeedsParenthesesLast(parent, child);
            }

            if (child.NodeType != ExpressionType.Constant || (parent.NodeType != ExpressionType.Negate && parent.NodeType != ExpressionType.NegateChecked))
            {
                return operatorPrecedence < operatorPrecedence2;
            }

            return true;
        }
        private static bool IsNeedsParentheses(Expression parent, Expression child)
        {
            if (child == null)
            {
                return false;
            }

            ExpressionType nodeType = parent.NodeType;
            if (nodeType <= ExpressionType.Increment)
            {
                if (nodeType != ExpressionType.Decrement && nodeType != ExpressionType.Increment)
                {
                    return NeedsParenthesesPrecedence(parent, child);
                }
            }
            else if (nodeType != ExpressionType.Unbox && nodeType != ExpressionType.IsTrue && nodeType != ExpressionType.IsFalse)
            {
                return NeedsParenthesesPrecedence(parent, child);
            }

            return true;
        }

        protected override SqlBuilder Join(BinaryExpression expression, SqlBuilder sqlBuilder)
        {
            ExpressionToSqlProvider.Join(expression.Left, sqlBuilder);
            int length = sqlBuilder.Length;
            ExpressionToSqlProvider.Join(expression.Right, sqlBuilder);
            if (sqlBuilder.Length - length == 5 && sqlBuilder.Sql.EndsWith("null"))
            {
                OperatorParser(expression.NodeType, length, sqlBuilder, useIs: true);
            }
            else
            {
                OperatorParser(expression.NodeType, length, sqlBuilder);
            }

            return sqlBuilder;
        }

        protected override SqlBuilder Where(BinaryExpression expression, SqlBuilder sqlBuilder)
        {
            if (IsNeedsParentheses(expression, expression.Left))
            {
                sqlBuilder += "(";
                ExpressionToSqlProvider.Where(expression.Left, sqlBuilder);
                sqlBuilder += ")";
            }
            else
            {
                ExpressionToSqlProvider.Where(expression.Left, sqlBuilder);
            }

            int length = sqlBuilder.Length;
            if (IsNeedsParentheses(expression, expression.Right))
            {
                sqlBuilder += "(";
                ExpressionToSqlProvider.Where(expression.Right, sqlBuilder);
                sqlBuilder += ")";
            }
            else
            {
                ExpressionToSqlProvider.Where(expression.Right, sqlBuilder);
            }

            if (sqlBuilder.Length - length == 5 && sqlBuilder.ToString().EndsWith("null"))
            {
                OperatorParser(expression.NodeType, length, sqlBuilder, useIs: true);
            }
            else
            {
                OperatorParser(expression.NodeType, length, sqlBuilder);
            }

            return sqlBuilder;
        }
    }
}
